package com.qidian.mes.sys.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qidian.mes.sys.mapper.DictionaryItemMapper;
import com.qidian.mes.sys.mapper.DictionaryMapper;
import com.qidian.mes.sys.service.DictionaryService;
import com.qidianit.mes.common.ThreadPoolUtils;
import com.qidianit.mes.common.exceptions.RedisCacheException;
import com.qidianit.mes.common.pojo.MesResult;
import com.qidianit.mes.common.util.FormatterUtil;
import com.qidianit.mes.pojo.Dictionary;
import com.qidianit.mes.pojo.DictionaryItem;
import org.apache.commons.lang3.ThreadUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author 戴着假发的程序员
 * @company 起点编程
 * @date 2025/7/25 9:34
 * @description
 */
@Service
public class DictionaryServiceImpl extends ServiceImpl<DictionaryMapper, Dictionary> implements DictionaryService {
    @Autowired
    private DictionaryMapper dictionaryMapper;
    @Autowired
    private DictionaryItemMapper dictionaryItemMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Value("${CACHE_DICT_KEY}")
    private String cacheDictKey;
    @Value("${CACHE_DICT_ITEMS_KEY}")
    private String cacheDictItemsKey;

    @Override
    public MesResult<List<Dictionary>> queryAll() {
        List<Dictionary> dictionaries = null;
        //  查询redis中是否存在对应的数据
        if(stringRedisTemplate.hasKey(cacheDictKey)){
            String dictsJson = stringRedisTemplate.opsForValue().get(cacheDictKey);
            // 将JSON转换为集合
            try {
                dictionaries = FormatterUtil.fromJsonToList(dictsJson, Dictionary.class);
            } catch (Exception e) {
                // 出异常，说明转换失败，将这个key直接删除
                stringRedisTemplate.delete(cacheDictKey);
            }
        }
        if(dictionaries == null || dictionaries.isEmpty()){
            // 说明Redis中没有查询到数据，我们就从数据库直接查询
            dictionaries = dictionaryMapper.selectList(Wrappers.emptyWrapper());
            if(dictionaries == null || dictionaries.isEmpty()){
                // 为了防止缓存击穿，我们给redis放一个空的数据
                stringRedisTemplate.opsForValue().set(cacheDictKey, "");
                // 设置缓存的过期时间
                stringRedisTemplate.expire(cacheDictKey, 20, TimeUnit.SECONDS);
            }else {
                // 查询到之后将查询的数据给redis中存储一份,要将集合转换为JSON字符串
                stringRedisTemplate.opsForValue().set(cacheDictKey, FormatterUtil.toJsonString(dictionaries));
                // 设置缓存的过期时间,一般为了防止缓存雪崩，我们会将过期时间设置为随机时间，或者永不过期
                stringRedisTemplate.expire(cacheDictKey, 12 + (int) (Math.random() * 12), TimeUnit.HOURS);
            }
        }
        // 字典项处理
        if(dictionaries != null) {
            // 遍历字典，查询对应的字典项信息
            dictionaries.stream().forEach(dictionary -> {
                MesResult<List<DictionaryItem>> result = queryByDictCode(dictionary.getCode());
                // 给dictionary对象也设置items属性
                dictionary.setItems(result.getData());
            });
        }
        return MesResult.success(dictionaries);
    }

    @Override
    public MesResult<List<DictionaryItem>> queryByDictCode(String dictCode) {
        List<DictionaryItem> items = null;
        // 先查询redis是否存在数据
        if(!stringRedisTemplate.hasKey(cacheDictItemsKey)){
            // 没有对应的items的hash的可以，我们就给放一个
            stringRedisTemplate.opsForHash().put(cacheDictItemsKey, "NONL", "");
            // 设置key的存活时间
            stringRedisTemplate.expire(cacheDictItemsKey, 12 + (int)(Math.random() * 12), TimeUnit.HOURS);
        }
        // 判断有没有对应的Filed
        if(stringRedisTemplate.opsForHash().hasKey(cacheDictItemsKey, dictCode)) {
            // 存在对应的key，我们获取对应的数据
            try {
                String itemsJson = stringRedisTemplate.opsForHash().get(cacheDictItemsKey,dictCode).toString();
                items = FormatterUtil.fromJsonToList(itemsJson, DictionaryItem.class);
            } catch (Exception e) {
                // 如果转换不了，或者出现异常，就直接删除对应的filed
                stringRedisTemplate.opsForHash().delete(cacheDictItemsKey, dictCode);
            }
        }
        // 判断是否已经从redis中获取到对应的items
        if(items == null || items.isEmpty()){
            // 没有获取到就从数据库查询
            items = dictionaryItemMapper.selectList(new QueryWrapper<DictionaryItem>().eq("dict_code", dictCode));
            if(items == null || items.isEmpty()){
                stringRedisTemplate.opsForHash().put(cacheDictItemsKey, dictCode, "");
            }else{
                // 给redis放一份
                stringRedisTemplate.opsForHash().put(cacheDictItemsKey, dictCode, FormatterUtil.toJsonString(items));
            }
        }
        return MesResult.success(items);
    }

    @Override
    public MesResult<Boolean> updateDictById(Dictionary dictionary) {
        // 先删除缓存
        stringRedisTemplate.delete(cacheDictKey);
        // 需改MySQL
        this.updateById(dictionary);
        // 等一会再删除缓存
        // 在子线程中删除缓存，子线程是要缓存起来的，我们使用我们之前定义的线程池工具类
        // 将我们的任务放在一个线程池中，这个线程是一个全局的线程池，即便主线程执行结束，子线程也会被线程池缓存起来。
        ThreadPoolUtils.submit(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            // 如果删除失败，要重试
            int count = 0;
            while(!stringRedisTemplate.delete(cacheDictKey)){
                try {
                    Thread.sleep(100); // 等待100毫秒重试一次
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if( ++count > 5){
                    // 将清理失败的key缓存到Redis中
                    stringRedisTemplate.opsForList().leftPush("CLEAR_CACHE_KEYS", cacheDictKey);
                    // 重试5次失败，就抛出异常
                    throw new RedisCacheException("Redis缓存清理异常，缓存:" + cacheDictKey + "清理失败");
                }
            }
        });
        return MesResult.success(true);
    }
}
